using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Drawing;
using Common.Utilities;

namespace PdfLib
{
    /// <summary>
    /// handles text positions, font changes
    /// </summary>
    public class PdfTextReader
    {
        #region settings
        public const float PageScaleFactor = 0.75f;
        public const double WordSpacing = 0.4;
        public const double LineSpacing = 0.5; // ratio of font size
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="pageNum"></param>
        /// <returns></returns>
        public static List<PdfTextToken> ReadPageContentAsTokens(string filePath, int pageNum)
        {
            if (!File.Exists(filePath))
            {
                throw new Exception("Unable to find file "+filePath);
            }

            List<PdfTextToken> txtTokens = new List<PdfTextToken>();
            PDFPARSERLib.Document pdfDoc = new PDFPARSERLib.Document();
            try
            {
                Dictionary<string, float> fontNameSizeMappings = new Dictionary<string, float>();
                if (pdfDoc.Open(filePath, ""))
                {
                    PDFPARSERLib.Font pdfFont = pdfDoc.GetFirstFontResource();
                    while (pdfFont != null)
                    {
                        if (!fontNameSizeMappings.ContainsKey(pdfFont.BaseName))
                        {
                            fontNameSizeMappings.Add(pdfFont.BaseName, pdfFont.CapHeight);
                        }
                        pdfFont = pdfDoc.GetNextFontResource();
                    }

                    PDFPARSERLib.Text text = null;
                    pdfDoc.PageNo = pageNum;
                    PDFPARSERLib.Page pdfPage = pdfDoc.Page;
                    object[] dimensions = (object[])pdfPage.CropBox;
                    SizeF pageSize=new SizeF();
                    if (dimensions != null && dimensions.Length == 4)
                    {
                        float pageHeight = float.Parse(dimensions[3].ToString());
                        float pageWidth = float.Parse(dimensions[2].ToString());
                        pageSize = new SizeF(pageWidth, pageHeight);
                    }
                    PDFPARSERLib.Content content = pdfDoc.Page.Content;
                    if (content != null)
                    {
                        text = content.GetNextText();
                        if(text==null)
                        {
                            return new List<PdfTextToken>();
                        }
                        float x = (float) ((object[]) text.XPos)[0];
                        float y = pageSize.Height - (float) ((object[]) text.YPos)[0];
                        PdfTextToken prevTxtToken = new PdfTextToken();
                        prevTxtToken.Text = text.UnicodeString.Trim();
                        prevTxtToken.FontSize = text.FontSize;
                        if (fontNameSizeMappings != null && fontNameSizeMappings.Count > 0)
                        {
                            foreach (string fontName in fontNameSizeMappings.Keys)
                            {
                                if (fontNameSizeMappings[fontName] == prevTxtToken.FontSize)
                                {
                                    prevTxtToken.FontName = fontName;
                                    break;
                                }
                            }
                        }
                        
                        prevTxtToken.Position=new PointF(x,y);
                        prevTxtToken.TextRect=new RectangleF(x,y,text.Width,text.FontSize);

                        while (text != null)
                        {
                            if(text.UnicodeString !=null)
                            {
                                float currentX = (float)((object[])text.XPos)[0];
                                float currentY = pageSize.Height - (float)((object[])text.YPos)[0];
                                if (Math.Abs(currentY - prevTxtToken.Position.Y) > LineSpacing * prevTxtToken.FontSize)
                                {
                                    if (!string.IsNullOrEmpty(prevTxtToken.Text))
                                    {
                                        txtTokens.Add(prevTxtToken);
                                        prevTxtToken = new PdfTextToken();
                                        prevTxtToken.Text = text.UnicodeString.Trim();
                                        prevTxtToken.FontSize = text.FontSize;
                                        if (fontNameSizeMappings != null && fontNameSizeMappings.Count > 0)
                                        {
                                            foreach (string fontName in fontNameSizeMappings.Keys)
                                            {
                                                if (fontNameSizeMappings[fontName] == prevTxtToken.FontSize)
                                                {
                                                    prevTxtToken.FontName = fontName;
                                                    break;
                                                }
                                            }
                                        }
                                        prevTxtToken.Position = new PointF(currentX, currentY);
                                        prevTxtToken.TextRect =
                                            new RectangleF(currentX, currentY, text.Width, text.FontSize);
                                    }
                                    else
                                    {
                                        prevTxtToken = new PdfTextToken();
                                        prevTxtToken.Text = text.UnicodeString.Trim();
                                        prevTxtToken.FontSize = text.FontSize;
                                        if (fontNameSizeMappings != null && fontNameSizeMappings.Count > 0)
                                        {
                                            foreach (string fontName in fontNameSizeMappings.Keys)
                                            {
                                                if (fontNameSizeMappings[fontName] == prevTxtToken.FontSize)
                                                {
                                                    prevTxtToken.FontName = fontName;
                                                    break;
                                                }
                                            }
                                        }
                                        prevTxtToken.Position = new PointF(currentX, currentY);
                                        prevTxtToken.TextRect =
                                            new RectangleF(currentX, currentY, text.Width, text.FontSize);
                                    }
                                }
                                else if (Math.Abs(currentX - prevTxtToken.Position.X - prevTxtToken.TextRect.Width) > WordSpacing)
                                {
                                    if (!string.IsNullOrEmpty(prevTxtToken.Text))
                                    {
                                        txtTokens.Add(prevTxtToken);
                                        prevTxtToken = new PdfTextToken();
                                        prevTxtToken.Text = text.UnicodeString.Trim();
                                        prevTxtToken.FontSize = text.FontSize;
                                        if (fontNameSizeMappings != null && fontNameSizeMappings.Count > 0)
                                        {
                                            foreach (string fontName in fontNameSizeMappings.Keys)
                                            {
                                                if (fontNameSizeMappings[fontName] == prevTxtToken.FontSize)
                                                {
                                                    prevTxtToken.FontName = fontName;
                                                    break;
                                                }
                                            }
                                        }
                                        prevTxtToken.Position = new PointF(currentX, currentY);
                                        prevTxtToken.TextRect =
                                            new RectangleF(currentX, currentY, text.Width, text.FontSize);
                                    }
                                    else
                                    {
                                        prevTxtToken = new PdfTextToken();
                                        prevTxtToken.Text = text.UnicodeString.Trim();
                                        prevTxtToken.FontSize = text.FontSize;
                                        if (fontNameSizeMappings != null && fontNameSizeMappings.Count > 0)
                                        {
                                            foreach (string fontName in fontNameSizeMappings.Keys)
                                            {
                                                if (fontNameSizeMappings[fontName] == prevTxtToken.FontSize)
                                                {
                                                    prevTxtToken.FontName = fontName;
                                                    break;
                                                }
                                            }
                                        }
                                        prevTxtToken.Position = new PointF(currentX, currentY);
                                        prevTxtToken.TextRect =
                                            new RectangleF(currentX, currentY, text.Width, text.FontSize);
                                    }
                                }
                                else
                                {
                                    prevTxtToken.Text += text.UnicodeString.Trim();
                                    prevTxtToken.FontSize = Math.Max(prevTxtToken.FontSize, text.FontSize);
                                    if (fontNameSizeMappings != null && fontNameSizeMappings.Count > 0)
                                    {
                                        foreach (string fontName in fontNameSizeMappings.Keys)
                                        {
                                            if (fontNameSizeMappings[fontName] == prevTxtToken.FontSize)
                                            {
                                                prevTxtToken.FontName = fontName;
                                                break;
                                            }
                                        }
                                    }
                                    prevTxtToken.TextRect =
                                        RectangleF.Union(prevTxtToken.TextRect,
                                                         new RectangleF(currentX, currentY, text.Width, text.FontSize));
                                    prevTxtToken.Position = new PointF(prevTxtToken.TextRect.X, prevTxtToken.TextRect.Y);
                                }
                            }
                            
                            text = content.GetNextText();
                        }
                    }
                }
            }
            catch(Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
            }
            finally
            {
                pdfDoc.Close();
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
            return txtTokens;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfFilePath"></param>
        /// <param name="pageNum"></param>
        /// <param name="minLineSpacing"></param>
        /// <returns></returns>
        public static List<PdfTextLine> ReadPageContentAsLines(string pdfFilePath, int pageNum, double minLineSpacing)
        {
            List<PdfTextToken> tokens = ReadPageContentAsTokens(pdfFilePath, pageNum);
            List<PdfTextLine> lines = CreateLines(tokens);
            lines = MergeLines(lines, minLineSpacing);
            return lines;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="pageNum"></param>
        /// <returns></returns>
        public static string ReadPageContent(string filePath, int pageNum)
        {
            List<PdfTextToken> txtTokenList = PdfTextReader.ReadPageContentAsTokens(filePath, pageNum);
            List<PdfTextLine> lines = PdfTextReader.CreateLines(txtTokenList);
            lines = PdfTextReader.MergeLines(lines, LineSpacing);
            StringBuilder buffer=new StringBuilder();
            foreach(PdfTextLine line in lines)
            {
                StringBuilder lineContent = new StringBuilder();
                for(int i=0;i<line.TextTokens.Count;i++)
                {
                    lineContent.Append(line.TextTokens[i].Text);
                    if(i<line.TextTokens.Count -1)
                    {
                        lineContent.Append(" ");
                    }
                }
                buffer.Append(lineContent.ToString() + Environment.NewLine);
            }
            return buffer.ToString().Trim();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="txtTokens"></param>
        /// <returns></returns>
        public static List<PdfTextLine> CreateLines(List<PdfTextToken> txtTokens)
        {
            txtTokens.Sort();
            PdfTextLine lastLine = new PdfTextLine();
            lastLine.TextTokens = new List<PdfTextToken>();
            List<PdfTextLine> lines = new List<PdfTextLine>();
            foreach (PdfTextToken txtToken in txtTokens)
            {
                if (lastLine.Rect.IsEmpty)
                {
                    lastLine = new PdfTextLine();
                    lastLine.Rect = txtToken.TextRect;
                    lastLine.TextTokens.Add(txtToken);
                }
                bool sameLine = false;
                Range<float> lastLineYRange = new Range<float>();
                lastLineYRange.FromValue = lastLine.Rect.Y;
                lastLineYRange.ToValue = lastLine.Rect.Y + lastLine.Rect.Height;
                Range<float> thisTxtYRange = new Range<float>();
                thisTxtYRange.FromValue = txtToken.Position.Y;
                thisTxtYRange.ToValue = txtToken.Position.Y + txtToken.TextRect.Height;
                if (lastLineYRange.OverlapWith(thisTxtYRange))
                    sameLine = true;
                else
                    sameLine = false;

                if (sameLine)
                {
                    lastLine.Rect = RectangleF.Union(lastLine.Rect, txtToken.TextRect);
                    lastLine.TextTokens.Add(txtToken);
                }
                else
                {
                    lines.Add(lastLine);
                    lastLine = new PdfTextLine();
                    lastLine.TextTokens=new List<PdfTextToken>();
                    lastLine.TextTokens.Add(txtToken);
                    lastLine.Rect = txtToken.TextRect;
                }
            }
            if (!PdfTextLine.IsEmpty(lastLine))
                lines.Add(lastLine);
            lines.Sort();
            return lines;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="lines"></param>
        /// <param name="minLineSpacing">absolute line spacing</param>
        /// <returns></returns>
        public static List<PdfTextLine> MergeLines(List<PdfTextLine> lines, 
            double minLineSpacing)
        {
            List<PdfTextLine> mergedLines = new List<PdfTextLine>();
            if (lines != null && lines.Count > 0)
            {
                PdfTextLine lastLine = PdfTextLine.Empty;
                for (int i = 0; i < lines.Count; i++)
                {
                    if (PdfTextLine.IsEmpty(lastLine))
                    {
                        lastLine = lines[i];
                    }
                    else
                    {
                        RectangleF lastRect = lastLine.Rect;
                        RectangleF currentRect = lines[i].Rect;
                        //lastRect.Inflate(fontSize, fontSize);
                        //currentRect.Inflate(fontSize, fontSize);
                        if (currentRect.Y < lastRect.Y + minLineSpacing)
                        {
                            lastRect = RectangleF.Union(lastLine.Rect, lines[i].Rect);
                            lastLine.Rect = lastRect;
                            lastLine.TextTokens.AddRange(lines[i].TextTokens.ToArray());
                        }
                        else
                        {
                            mergedLines.Add(lastLine);
                            lastLine = lines[i];
                        }
                    }
                }
                if (!PdfTextLine.IsEmpty(lastLine))
                {
                    mergedLines.Add(lastLine);
                }
            }
            return mergedLines;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfFilePath"></param>
        /// <param name="fromPageNum"></param>
        /// <param name="fromPos"></param>
        /// <param name="toPageNum"></param>
        /// <param name="toPos"></param>
        /// <returns></returns>
        public static string GetContent(string pdfFilePath, int fromPageNum, PointF fromPos, int toPageNum, PointF toPos)
        {
            Single oldY = 0;
            System.Text.StringBuilder buffer = new StringBuilder();
            PDFPARSERLib.Document pdfDoc = new PDFPARSERLib.Document();
            if (pdfDoc.Open(pdfFilePath, ""))
            {
                PDFPARSERLib.Content content = new PDFPARSERLib.Content();
                PDFPARSERLib.Text text = null;
                int pageCount = pdfDoc.PageCount;
                int pageNum = fromPageNum;
                bool startRead = false;
                while (pageNum <= pageCount && pageNum <= toPageNum)
                {
                    pdfDoc.PageNo = pageNum;
                    PDFPARSERLib.Page page = pdfDoc.Page;
                    SizeF size = new SizeF();
                    object[] dimensions = (object[])page.CropBox;
                    if (dimensions != null && dimensions.Length == 4)
                    {
                        float pageHeight = float.Parse(dimensions[3].ToString());
                        float pageWidth = float.Parse(dimensions[2].ToString());
                        size = new SizeF(pageWidth, pageHeight);
                    }

                    PDFPARSERLib.Document pageDoc = page.Document;
                    content = pageDoc.Page.Content;
                    if (content == null)
                        text = null;
                    else
                        text = content.GetNextText();


                    while (text != null)
                    {
                        Single y = (Single)((object[])text.YPos)[0];
                        y = size.Height - y;

                        if (pageNum == toPageNum && y >= toPos.Y)
                            break;

                        if (!startRead)
                        {
                            if (y >= fromPos.Y)
                            {
                                startRead = true;
                                buffer.Append(text.UnicodeString);
                            }
                        }
                        else
                        {
                            string nodeText = text.UnicodeString;
                            if (Math.Abs(oldY - y) > 4)
                                buffer.Append(Environment.NewLine + nodeText);
                            else
                                buffer.Append(" " + nodeText);
                        }

                        oldY = y;
                        text = content.GetNextText();
                    }

                    pageNum++;
                }
            }
            else
            {
                pdfDoc.Close();
                throw new Exception("Unable to open pdf file");
            }
            return buffer.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfFilePath"></param>
        /// <param name="pageNum"></param>
        /// <param name="minRectWidth"></param>
        /// <param name="minRectHeight"></param>
        /// <returns></returns>
        public static string ReadBoxedContent(string pdfFilePath, int pageNum, float minRectWidth, float minRectHeight)
        {
            Image pageImg = PdfImageReader.GetPageImage(pdfFilePath, pageNum);
            SizeF pageSize = PdfPropertyReader.GetPageSize(pdfFilePath, pageNum);

            if(pageImg !=null)
            {
                ImageProcesser imgProcessor=new ImageProcesser(new Bitmap(pageImg), pageSize);
                List<Rectangle> rects = imgProcessor.ReadRectangles();
                if(rects !=null && rects.Count>0)
                {
                    foreach(Rectangle rect in rects)
                    {
                        if(rect.Width >=minRectWidth && rect.Height >= minRectHeight)
                        {
                            return PdfTextReader.GetContent(pdfFilePath, pageNum,
                                                            new PointF(rect.Left, rect.Top),
                                                            pageNum,
                                                            new PointF(rect.X + rect.Width, rect.Y + rect.Height));
                        }
                    }
                }
            }
            else
            {
                List<RectangleF> rects= PdfDrawingInstructionReader.GeRectangles(pdfFilePath, pageNum);
                if (rects != null && rects.Count > 0)
                {
                    foreach (RectangleF rect in rects)
                    {
                        if (rect.Width >= minRectWidth && rect.Height >= minRectHeight)
                        {
                            return PdfTextReader.GetContent(pdfFilePath, pageNum,
                                                            new PointF(rect.Left, rect.Top),
                                                            pageNum,
                                                            new PointF(rect.X + rect.Width, rect.Y + rect.Height));
                        }
                    }
                }
            }
            return PdfTextReader.ReadPageContent(pdfFilePath, pageNum);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="pageNum"></param>
        /// <returns></returns>
        public static Image GetPageTextLayout(string filePath, int pageNum)
        {
            SizeF pageSize = PdfPropertyReader.GetPageSize(filePath, pageNum);
            Bitmap img = new Bitmap((int)pageSize.Width, (int)pageSize.Height);
            Graphics g = Graphics.FromImage(img);
            g.Clear(Color.White);

            List<PdfTextToken> tokens = PdfTextReader.ReadPageContentAsTokens(filePath, pageNum);
            tokens.Sort();
            foreach (PdfTextToken token in tokens)
            {
                System.Drawing.Font font = new Font(token.FontName, token.FontSize * PageScaleFactor);
                g.DrawString(token.Text, font, new SolidBrush(Color.Black), token.Position);
            }
            g.Dispose();
            return img;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfFilePath"></param>
        /// <returns></returns>
        public static string ReadAll(string pdfFilePath)
        {
            StringBuilder buffer=new StringBuilder();
            int pageCount = PdfPropertyReader.GetPageCount(pdfFilePath);
            for(int i=1;i<=pageCount;i++)
            {
                string pageContent = PdfTextReader.ReadPageContent(pdfFilePath, i);
                if (buffer.Length > 0)
                    buffer.AppendLine();
                buffer.Append(pageContent);
            }
            return buffer.ToString();
        }
    }
}
